// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/folder_image.h"

#include <vector>

#include "base/macros.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_item_list.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/strings/grit/ui_strings.h"

namespace app_list {

namespace {

    const int kItemIconDimension = 16;

    // Gets the size of a small app icon inside the folder icon.
    gfx::Size ItemIconSize()
    {
        return gfx::Size(kItemIconDimension, kItemIconDimension);
    }

    // Generates the folder icon with the top 4 child item icons laid in 2x2 tile.
    class FolderImageSource : public gfx::CanvasImageSource {
    public:
        typedef std::vector<gfx::ImageSkia> Icons;

        FolderImageSource(const Icons& icons, const gfx::Size& size);
        ~FolderImageSource() override;

    private:
        void DrawIcon(gfx::Canvas* canvas,
            const gfx::ImageSkia& icon,
            const gfx::Size icon_size,
            int x,
            int y);

        // gfx::CanvasImageSource overrides:
        void Draw(gfx::Canvas* canvas) override;

        Icons icons_;
        gfx::Size size_;

        DISALLOW_COPY_AND_ASSIGN(FolderImageSource);
    };

    FolderImageSource::FolderImageSource(const Icons& icons, const gfx::Size& size)
        : gfx::CanvasImageSource(size, false)
        , icons_(icons)
        , size_(size)
    {
        DCHECK(icons.size() <= kNumFolderTopItems);
    }

    FolderImageSource::~FolderImageSource()
    {
    }

    void FolderImageSource::DrawIcon(gfx::Canvas* canvas,
        const gfx::ImageSkia& icon,
        const gfx::Size icon_size,
        int x,
        int y)
    {
        if (icon.isNull())
            return;

        gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(
            icon, skia::ImageOperations::RESIZE_BEST, icon_size));
        canvas->DrawImageInt(resized, 0, 0, resized.width(), resized.height(), x, y,
            resized.width(), resized.height(), true);
    }

    void FolderImageSource::Draw(gfx::Canvas* canvas)
    {
        SkPaint paint;
        // Draw circle for folder bubble.
        gfx::PointF bubble_center(size().width() / 2, size().height() / 2);
        bubble_center.Offset(0, -kFolderBubbleOffsetY);
        paint.setStyle(SkPaint::kFill_Style);
        paint.setAntiAlias(true);
        paint.setColor(kFolderBubbleColor);
        canvas->sk_canvas()->drawCircle(bubble_center.x(), bubble_center.y(),
            kFolderBubbleRadius, paint);

        if (icons_.size() == 0)
            return;

        // Draw top items' icons.
        const gfx::Size item_icon_size(ItemIconSize());
        std::vector<gfx::Rect> top_icon_bounds = FolderImage::GetTopIconsBounds(gfx::Rect(size()));

        for (size_t i = 0; i < kNumFolderTopItems && i < icons_.size(); ++i) {
            DrawIcon(canvas, icons_[i], item_icon_size, top_icon_bounds[i].x(),
                top_icon_bounds[i].y());
        }
    }

} // namespace

FolderImage::FolderImage(AppListItemList* item_list)
    : item_list_(item_list)
{
    item_list_->AddObserver(this);
}

FolderImage::~FolderImage()
{
    for (auto* item : top_items_)
        item->RemoveObserver(this);
    item_list_->RemoveObserver(this);
}

void FolderImage::UpdateIcon()
{
    for (auto* item : top_items_)
        item->RemoveObserver(this);
    top_items_.clear();

    for (size_t i = 0; i < kNumFolderTopItems && i < item_list_->item_count();
         ++i) {
        AppListItem* item = item_list_->item_at(i);
        item->AddObserver(this);
        top_items_.push_back(item);
    }
    RedrawIconAndNotify();
}

const gfx::ImageSkia& FolderImage::GetTopIcon(size_t item_index) const
{
    CHECK_LT(item_index, top_items_.size());
    return top_items_[item_index]->icon();
}

// static
std::vector<gfx::Rect> FolderImage::GetTopIconsBounds(
    const gfx::Rect& folder_icon_bounds)
{
    const int delta_to_center = 1;
    gfx::Point icon_center = folder_icon_bounds.CenterPoint();
    std::vector<gfx::Rect> top_icon_bounds;

    // Get the top left icon bounds.
    int left_x = icon_center.x() - kItemIconDimension - delta_to_center;
    int top_y = icon_center.y() - kItemIconDimension - delta_to_center;
    gfx::Rect top_left(left_x, top_y, kItemIconDimension, kItemIconDimension);
    top_icon_bounds.push_back(top_left);

    // Get the top right icon bounds.
    int right_x = icon_center.x() + delta_to_center;
    gfx::Rect top_right(right_x, top_y, kItemIconDimension, kItemIconDimension);
    top_icon_bounds.push_back(top_right);

    // Get the bottom left icon bounds.
    int bottom_y = icon_center.y() + delta_to_center;
    gfx::Rect bottom_left(left_x, bottom_y, kItemIconDimension,
        kItemIconDimension);
    top_icon_bounds.push_back(bottom_left);

    // Get the bottom right icon bounds.
    gfx::Rect bottom_right(right_x, bottom_y, kItemIconDimension,
        kItemIconDimension);
    top_icon_bounds.push_back(bottom_right);

    return top_icon_bounds;
}

gfx::Rect FolderImage::GetTargetIconRectInFolderForItem(
    AppListItem* item,
    const gfx::Rect& folder_icon_bounds) const
{
    for (size_t i = 0; i < top_items_.size(); ++i) {
        if (item->id() == top_items_[i]->id()) {
            std::vector<gfx::Rect> rects = GetTopIconsBounds(folder_icon_bounds);
            return rects[i];
        }
    }

    gfx::Rect target_rect(folder_icon_bounds);
    target_rect.ClampToCenteredSize(ItemIconSize());
    return target_rect;
}

void FolderImage::AddObserver(FolderImageObserver* observer)
{
    observers_.AddObserver(observer);
}

void FolderImage::RemoveObserver(FolderImageObserver* observer)
{
    observers_.RemoveObserver(observer);
}

void FolderImage::ItemIconChanged()
{
    // Note: Must update the image only (cannot simply call UpdateIcon), because
    // UpdateIcon removes and re-adds the FolderImage as an observer of the
    // AppListItems, which causes the current iterator to call ItemIconChanged
    // again, and goes into an infinite loop.
    RedrawIconAndNotify();
}

void FolderImage::OnListItemAdded(size_t index, AppListItem* item)
{
    if (index < kNumFolderTopItems)
        UpdateIcon();
}

void FolderImage::OnListItemRemoved(size_t index, AppListItem* item)
{
    if (index < kNumFolderTopItems)
        UpdateIcon();
}

void FolderImage::OnListItemMoved(size_t from_index,
    size_t to_index,
    AppListItem* item)
{
    if (from_index < kNumFolderTopItems || to_index < kNumFolderTopItems)
        UpdateIcon();
}

void FolderImage::RedrawIconAndNotify()
{
    FolderImageSource::Icons top_icons;
    for (const auto* item : top_items_)
        top_icons.push_back(item->icon());

    const gfx::Size icon_size = gfx::Size(kGridIconDimension, kGridIconDimension);
    icon_ = gfx::ImageSkia(new FolderImageSource(top_icons, icon_size), icon_size);

    FOR_EACH_OBSERVER(FolderImageObserver, observers_, OnFolderImageUpdated());
}

} // namespace app_list
